﻿using System;
using System.Globalization;

namespace Ewk.Math.ComputationTypes
{
    /// <summary>
    /// Represents a value that can be calculated with.
    /// </summary>
    /// <typeparam name="T">The <see cref="Type"/> of the internal <see cref="Value"/>.</typeparam>
    public abstract class Computable<T> : IComparable, IComparable<Computable<T>>, IEquatable<Computable<T>>
        where T : struct, IComparable, IFormattable, IConvertible, IComparable<T>, IEquatable<T>
    {
        private static IComputableFactory<T> _factory;

        /// <summary>
        /// The factory that can create instances of the <see cref="Computable{T}"/>.
        /// </summary>
        public static IComputableFactory<T> Factory
        {
            get { return _factory ?? (_factory = ComputableFactoryProvider.ResolveFactory<T>()); }
        }

        #region Constructors
        /// <summary>
        /// Initializes a new instance.
        /// </summary>
        protected Computable()
            : this(default(T))
        {
        }

        /// <summary>
        /// Initializes a new instannce.
        /// </summary>
        /// <param name="value">The internal value of the instance.</param>
        protected Computable(T value)
        {
            Value = value;
        }
        #endregion

        /// <summary>
        /// The internal value of this instance.
        /// </summary>
        public T Value { get; protected set; }

        /// <summary>
        /// Adds the specified <see cref="Computable{T}"/> to this <see cref="Computable{T}"/>.
        /// </summary>
        /// <param name="other">The <see cref="Computable{T}"/> to add.</param>
        /// <returns>The sum of the specified <see cref="Computable{T}"/> added to this <see cref="Computable{T}"/>.</returns>
        public abstract Computable<T> Add(Computable<T> other);

        /// <summary>
        /// Subtracts the specified <see cref="Computable{T}"/> from this <see cref="Computable{T}"/>.
        /// </summary>
        /// <param name="other">The <see cref="Computable{T}"/> to subtract.</param>
        /// <returns>The sum of the specified <see cref="Computable{T}"/> subtracted from this <see cref="Computable{T}"/>.</returns>
        public abstract Computable<T> Subtract(Computable<T> other);

        /// <summary>
        /// Multiplies the specified <see cref="Computable{T}"/> with this <see cref="Computable{T}"/>.
        /// </summary>
        /// <param name="other">The <see cref="Computable{T}"/> to multiply with.</param>
        /// <returns>The sum of the specified <see cref="Computable{T}"/> multiplied with this <see cref="Computable{T}"/>.</returns>
        public abstract Computable<T> Multiply(Computable<T> other);

        /// <summary>
        /// Divides this <see cref="Computable{T}"/> by the specified <see cref="Computable{T}"/>.
        /// </summary>
        /// <param name="other">The <see cref="Computable{T}"/> to divide with.</param>
        /// <returns>The sum of this <see cref="Computable{T}"/> divided by the specified <see cref="Computable{T}"/>.</returns>
        public abstract Computable<T> Divide(Computable<T> other);

        /// <summary>
        /// Computes the square root of this <see cref="Computable{T}"/>.
        /// </summary>
        /// <returns>The square root of this <see cref="Computable{T}"/>.</returns>
        public abstract Computable<T> Sqrt();

        /// <summary>
        /// Computes the result of this <see cref="Computable{T}"/> raised by the specified <see cref="Computable{T}"/>.
        /// </summary>
        /// <param name="power">The <see cref="Computable{T}"/> to raise by.</param>
        /// <returns>A <see cref="Computable{T}"/> that represents this <see cref="Computable{T}"/>
        /// raised by the specified <see cref="Computable{T}"/>.</returns>
        public abstract Computable<T> Pow(Computable<T> power);

        #region Equality
        /// <summary>
        /// Returns a value indicating whether this instance and the specified <see cref="object"/> represent the same value.
        /// </summary>
        /// <param name="obj">The <see cref="object"/> to compare with.</param>
        /// <returns>A value indicating whether this instance and the specified <see cref="object"/> represent the same value.</returns>
        public override bool Equals(object obj)
        {
            return Equals(obj as Computable<T>);
        }

        /// <summary>
        /// Returns a value indicating whether this instance and the specified <see cref="Computable{T}"/> represent the same value.
        /// </summary>
        /// <param name="other">The <see cref="Computable{T}"/> number to compare with.</param>
        /// <returns>A value indicating whether this instance and the specified <see cref="Computable{T}"/> represent the same value.</returns>
        public bool Equals(Computable<T> other)
        {
            return CompareTo(other) == 0;
        }

        /// <summary>
        /// Returns a value indicating whether this instance and the specified <see cref="object"/> represent the same value.
        /// </summary>
        /// <param name="obj">The <see cref="object"/> to compare with.</param>
        /// <returns>A value indicating whether this instance and the specified <see cref="object"/> represent the same value.</returns>
        public bool AlmostEquals(object obj)
        {
            return AlmostEquals(obj as Computable<T>);
        }

        /// <summary>
        /// Returns a value indicating whether this instance and the specified <see cref="Computable{T}"/> represent the same value.
        /// </summary>
        /// <param name="other">The <see cref="Computable{T}"/> number to compare with.</param>
        /// <returns>A value indicating whether this instance and the specified <see cref="Computable{T}"/> represent the same value.</returns>
        public virtual bool AlmostEquals(Computable<T> other)
        {
            if (ReferenceEquals(null, other)) return false;

            var result = this - other;

            return result < Factory.Epsilon;
        }

        /// <summary>
        /// Compares this instance to the specified <see cref="object"/> and
        /// returns an <see cref="int"/> that indicates whether the value of this
        /// instance is considered smaller, equal or greater than the specified <see cref="object"/>.
        /// </summary>
        /// <param name="obj">The <see cref="object"/> to compare with.</param>
        /// <returns>
        /// An <see cref="int"/> that indicates whether the value of this
        /// instance is considered smaller, equal or greater than the specified <see cref="object"/>.
        /// </returns>
        public int CompareTo(object obj)
        {
            return CompareTo(obj as Computable<T>);
        }

        /// <summary>
        /// Compares this instance to the specified <see cref="Computable{T}"/> and
        /// returns an <see cref="int"/> that indicates whether the value of this
        /// instance is considered smaller, equal or greater than the specified <see cref="Computable{T}"/>.
        /// </summary>
        /// <param name="other">The <see cref="Computable{T}"/> to compare with.</param>
        /// <returns>
        /// An <see cref="int"/> that indicates whether the value of this
        /// instance is considered smaller, equal or greater than the specified <see cref="Computable{T}"/>.
        /// </returns>
        public int CompareTo(Computable<T> other)
        {
            if (ReferenceEquals(null, other)) return 1;

            return ReferenceEquals(this, other)
                       ? 0
                       : Value.CompareTo(other.Value);
        }

        /// <summary>
        /// Serves as a hash function.
        /// </summary>
        /// <returns>An <see cref="int"/> that represents the hash of this instance.</returns>
        public override int GetHashCode()
        {
            return Value.GetHashCode();
        }
        #endregion

        /// <summary>
        /// Creates a <see cref="string"/> representation of this <see cref="Computable{T}"/>.
        /// </summary>
        public override string ToString()
        {
            return Value.ToString(CultureInfo.InvariantCulture);
        }

        #region Static members
        /// <summary>
        /// Adds two <see cref="Computable{T}"/> instances.
        /// </summary>
        public static Computable<T> Add(Computable<T> a, Computable<T> b)
        {
            if (a == null)
            {
                return b == null ? Factory.Zero : Factory.Create(b.Value);
            }

            if (b == null)
            {
                return Factory.Create(a.Value);
            }

            if (a.GetType() != b.GetType())
            {
                throw new InvalidOperationException("Types are not the same.");
            }

            return Factory.Create(a.Value).Add(b);
        }

        /// <summary>
        /// Subtracts <see cref="Computable{T}">b</see> from <see cref="Computable{T}">a</see>.
        /// </summary>
        public static Computable<T> Subtract(Computable<T> a, Computable<T> b)
        {
            if (a == null)
            {
                return b == null ? Factory.Zero : Factory.Zero.Subtract(b);
            }

            if (b == null)
            {
                return Factory.Create(a.Value);
            }

            if (a.GetType() != b.GetType())
            {
                throw new InvalidOperationException("Types are not the same.");
            }

            return Factory.Create(a.Value).Subtract(b);
        }

        /// <summary>
        /// Multiplies two <see cref="Computable{T}"/> instances.
        /// </summary>
        public static Computable<T> Multiply(Computable<T> a, Computable<T> b)
        {
            if (a == null || b == null)
            {
                return Factory.Zero;
            }

            if (a.GetType() != b.GetType())
            {
                throw new InvalidOperationException("Types are not the same.");
            }

            return Factory.Create(a.Value).Multiply(b);
        }

        /// <summary>
        /// Divides <see cref="Computable{T}">a</see> by <see cref="Computable{T}">b</see>.
        /// </summary>
        public static Computable<T> Divide(Computable<T> a, Computable<T> b)
        {
            if (b == null)
            {
                return null;
            }

            if (a == null)
            {
                return Factory.Zero;
            }

            if (a.GetType() != b.GetType())
            {
                throw new InvalidOperationException("Types are not the same.");
            }

            return Factory.Create(a.Value).Divide(b);
        }

        /// <summary>
        /// Computes the square root of the specified <see cref="Computable{T}"/>.
        /// </summary>
        /// <param name="value">The <see cref="Computable{T}"/> to calculate the square root from.</param>
        /// <returns>A <see cref="Computable{T}"/> that represents the square root of the specified <see cref="Computable{T}"/>.</returns>
        public static Computable<T> Sqrt(Computable<T> value)
        {
            return value == null ? Factory.Zero : Factory.Create(value.Value).Sqrt();
        }

        /// <summary>
        /// Computes <see cref="Computable{T}">a</see> to the power <see cref="Computable{T}">b</see>.
        /// </summary>
        /// <param name="a">The base of the power.</param>
        /// <param name="b">The value to raise <see cref="Computable{T}">a</see> with.</param>
        /// <returns>A <see cref="Computable{T}"/> that represents <see cref="Computable{T}">a</see> to the power <see cref="Computable{T}">b</see>.</returns>
        public static Computable<T> Pow(Computable<T> a, Computable<T> b)
        {
            if (a == null)
            {
                return null;
            }
            
            if (b == null)
            {
                return Factory.One;
            }

            if (a.GetType() != b.GetType())
            {
                throw new InvalidOperationException("Types are not the same.");
            }

            return Factory.Create(a.Value).Pow(b);
        }

        #region Overloaded operators
        /// <summary>
        /// Adds two <see cref="Computable{T}"/> instances.
        /// </summary>
        public static Computable<T> operator +(Computable<T> a, Computable<T> b)
        {
            return Add(a, b);
        }

        /// <summary>
        /// Adds the one representation of a <see cref="Computable{T}"/> to <see cref="Computable{T}">a</see> instances.
        /// </summary>
        public static Computable<T> operator ++(Computable<T> a)
        {
            return Add(a, Factory.One);
        }

        /// <summary>
        /// Subtracts <see cref="Computable{T}">b</see> from <see cref="Computable{T}">a</see>.
        /// </summary>
        public static Computable<T> operator -(Computable<T> a, Computable<T> b)
        {
            return Subtract(a, b);
        }

        /// <summary>
        /// Subtracts the one representation of a <see cref="Computable{T}"/> from <see cref="Computable{T}">a</see>.
        /// </summary>
        public static Computable<T> operator --(Computable<T> a)
        {
            return Subtract(a, Factory.One);
        }

        /// <summary>
        /// Multiplies two <see cref="Computable{T}"/> instances.
        /// </summary>
        public static Computable<T> operator *(Computable<T> a, Computable<T> b)
        {
            return Multiply(a, b);
        }

        /// <summary>
        /// Divides <see cref="Computable{T}">a</see> by <see cref="Computable{T}">b</see>.
        /// </summary>
        public static Computable<T> operator /(Computable<T> a, Computable<T> b)
        {
            return Divide(a, b);
        }

        /// <summary>
        /// Returns a value that indicates if <see cref="Computable{T}">a</see> is equal to <see cref="Computable{T}">b</see>.
        /// </summary>
        public static bool operator ==(Computable<T> a, Computable<T> b)
        {
            return ReferenceEquals(null, a)
                       ? ReferenceEquals(null, b)
                       : a.Equals(b);
        }

        /// <summary>
        /// Returns a value that indicates if <see cref="Computable{T}">a</see> is not equal to <see cref="Computable{T}">b</see>.
        /// </summary>
        public static bool operator !=(Computable<T> a, Computable<T> b)
        {
            return !(a == b);
        }

        /// <summary>
        /// Returns a value that indicates if <see cref="Computable{T}">a</see> is smaller than <see cref="Computable{T}">b</see>.
        /// </summary>
        public static bool operator <(Computable<T> a, Computable<T> b)
        {
            if (ReferenceEquals(null, a))
            {
                return !ReferenceEquals(null, b) && b.CompareTo(null) >= 0;
            }

            return a.CompareTo(b) < 0;
        }

        /// <summary>
        /// Returns a value that indicates if <see cref="Computable{T}">a</see> is greater than <see cref="Computable{T}">b</see>.
        /// </summary>
        public static bool operator >(Computable<T> a, Computable<T> b)
        {
            if (ReferenceEquals(null, a))
            {
                return !ReferenceEquals(null, b) && b.CompareTo(null) <= 0;
            }

            return a.CompareTo(b) > 0;
        }

        /// <summary>
        /// Returns a value that indicates if <see cref="Computable{T}">a</see> is equal to or smaller than <see cref="Computable{T}">b</see>.
        /// </summary>
        public static bool operator <=(Computable<T> a, Computable<T> b)
        {
            return !(a > b);
        }

        /// <summary>
        /// Returns a value that indicates if <see cref="Computable{T}">a</see> is equal to or greater than <see cref="Computable{T}">b</see>.
        /// </summary>
        public static bool operator >=(Computable<T> a, Computable<T> b)
        {
            return !(a < b);
        }

        /// <summary>
        /// Implicitly creates a <see cref="Computable{T}"/> from a value of <see cref="Type"/> T.
        /// </summary>
        /// <param name="value">The value that is used as internal value for the resulting <see cref="Computable{T}"/>.</param>
        /// <returns>A <see cref="Computable{T}"/> that represents the specified value.</returns>
        public static implicit operator Computable<T>(T value)
        {
            return Factory.Create(value);
        }
        #endregion
        #endregion
    }
}